﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text;
using System.Linq;
using System.ComponentModel;
using System.Runtime.Serialization;

using WEX.Logging.Interop;


namespace Microsoft.Reputation.Tests.FrontEndCommon
{
    /// <summary>
    /// Holds the possible object types supported by our DeliveryService
    /// </summary>
    public static class MrsObjectTypes
    {
        public const uint None = 0;
        public const uint File = 1;
        public const uint Application = 2;
        public const uint Publisher = 3;
        public const uint Url = 4;

        public const string NoneName = "None";
        public const string FileName = "File";
        public const string ApplicationName = "Application";
        public const string PublisherName = "Publisher";
        public const string UrlName = "URL";

        public static IList<string> DisplayNames
        {
            get
            {
                return new string[] {
                    MrsObjectTypes.NoneName,
                    MrsObjectTypes.FileName,
                    MrsObjectTypes.ApplicationName,
                    MrsObjectTypes.PublisherName,
                    MrsObjectTypes.UrlName
                };
            }
        }


        public static string ToDisplayName(uint type)
        {
            switch (type)
            {
                case MrsObjectTypes.None:
                    return MrsObjectTypes.NoneName;

                case MrsObjectTypes.File:
                    return MrsObjectTypes.FileName;

                case MrsObjectTypes.Application:
                    return MrsObjectTypes.ApplicationName;

                case MrsObjectTypes.Publisher:
                    return MrsObjectTypes.PublisherName;

                case MrsObjectTypes.Url:
                    return MrsObjectTypes.UrlName;

                default:
                    return String.Format("Unknown ({0:X8}) type", type);
            }
        }

        public static uint ToTypeId(string displayName)
        {
            if (MrsObjectTypes.FileName.Equals(displayName, StringComparison.OrdinalIgnoreCase))
            {
                return MrsObjectTypes.File;
            }
            else if (MrsObjectTypes.ApplicationName.Equals(displayName, StringComparison.OrdinalIgnoreCase))
            {
                return MrsObjectTypes.Application;
            }
            else if (MrsObjectTypes.PublisherName.Equals(displayName, StringComparison.OrdinalIgnoreCase))
            {
                return MrsObjectTypes.Publisher;
            }
            else if (MrsObjectTypes.UrlName.Equals(displayName, StringComparison.OrdinalIgnoreCase))
            {
                return MrsObjectTypes.Url;
            }
            else
            {
                return MrsObjectTypes.None;
            }
        }

        public static bool IsValidType(uint objType)
        {
            return ((objType == MrsObjectTypes.File) ||
                    (objType == MrsObjectTypes.Application) ||
                    (objType == MrsObjectTypes.Publisher) ||
                    (objType == MrsObjectTypes.Url));
        }
    }

    public class MrsRequestDataTypes
    {
        public const int Reputation = 0;
        public const int Metadata = 1;


        public static string ToDisplayName(IList<int> types)
        {
            if (types == null)
            {
                return "Metadata, Reputation";
            }

            bool isMetadata = false;
            bool isReputation = false;

            foreach (int curType in types)
            {
                if (curType == MrsRequestDataTypes.Metadata)
                {
                    isMetadata = true;
                }

                if (curType == MrsRequestDataTypes.Reputation)
                {
                    isReputation = true;
                }
            }

            if (isReputation && isMetadata)
            {
                return "Metadata, Reputation";
            }
            else
            {
                if (isReputation)
                {
                    return "Reputation";
                }
                else if (isMetadata)
                {
                    return "Metadata";
                }
                else
                {
                    return "None";
                }
            }
        }
    }

    public class MrsFilter
    {
        private long? lastStamp;
        private IList<int> filterTypes;
        private IList<string> filterMetadata;
        private IList<int> filterCategories;

        public MrsFilter(long? stamp, IList<int> types, IList<string> fields, IList<int> categories)
        {
            lastStamp = stamp;
            filterTypes = types;
            filterMetadata = fields;
            filterCategories = categories;
        }

        public long? LastStamp
        {
            get
            {
                return lastStamp;
            }
            set
            {
                lastStamp = value;
            }
        }

        public IList<int> Types
        {
            get
            {
                return filterTypes;
            }
            set
            {
                filterTypes = new List<int>(value);
            }
        }

        public IList<string> Metadata
        {
            get
            {
                return filterMetadata;
            }
            set
            {
                filterMetadata = new List<string>(value);
            }
        }

        public IList<int> Categories
        {
            get
            {
                return filterCategories;
            }
            set
            {
                filterCategories = new List<int>(value);
            }
        }
    }

    [Serializable]
    public class MrsMetadata
    {
        private string m_name;
        private object m_value;

        public MrsMetadata()
        {
        }

        public MrsMetadata(string name, object value)
        {
            this.m_name = name;
            this.m_value = value;
        }

        [ReadOnly(true)]
        public string Name
        {
            get
            {
                return this.m_name;
            }

            set
            {
                this.m_name = value;
            }
        }

        [ReadOnly(true)]
        public object Value
        {
            get
            {
                return this.m_value;
            }

            set
            {
                this.m_value = value;
            }
        }

        public override bool Equals(object obj)
        {
            bool isEqual = true;

            if ((obj == null) || (!obj.GetType().Equals(this.GetType())))
            {
                isEqual = false;
            }

            MrsMetadata otherObj = (MrsMetadata)(obj);

            if (!this.Name.Equals(otherObj.Name, StringComparison.OrdinalIgnoreCase))
            {
                isEqual = false;
            }

            if (!this.Value.GetType().Equals(otherObj.Value.GetType()))
            {
                isEqual = false;
            }

            if (!this.Compare(this.Value, otherObj.Value))
            {
                isEqual = false;
            }

            return isEqual;
        }

        private bool Compare(object srcObj, object tgtObj)
        {
            if ((srcObj == null) || (tgtObj == null))
            {
                return (srcObj == tgtObj);
            }

            if (!srcObj.GetType().Equals(tgtObj.GetType()))
            {
                return false;
            }

            if (srcObj.GetType().Equals(typeof(string)))
            {
                return ((string)(srcObj)).Equals(((string)(tgtObj)), StringComparison.OrdinalIgnoreCase);
            }

            if ((srcObj as IEnumerable) != null)
            {
                return Compare((srcObj as IEnumerable), (tgtObj as IEnumerable));
            }

            return srcObj.Equals(tgtObj);
        }

        /// <summary>
        /// Compares objects of IEnumerable types i.e. holding a collection
        /// </summary>
        /// <param name="srcObj"></param>
        /// <param name="tgtObj"></param>
        /// <returns></returns>
        private bool Compare(IEnumerable srcObj, IEnumerable tgtObj)
        {
            bool isMatch = true;
            IEnumerator srcEnum = srcObj.GetEnumerator();
            IEnumerator tgtEnum = tgtObj.GetEnumerator();

            while (isMatch)
            {
                bool srcMove = srcEnum.MoveNext();
                bool tgtMove = tgtEnum.MoveNext();

                if (srcMove != tgtMove)
                {
                    isMatch = false;
                    break;
                }

                if (!srcMove)
                {
                    break;
                }

                object srcItem = srcEnum.Current;
                object tgtItem = tgtEnum.Current;

                if (!Compare(srcItem, tgtItem))
                {
                    isMatch = false;
                    break;
                }
            }

            return isMatch;
        }

        public override int GetHashCode()
        {
            int hash = 0;

            if (this.Name != null)
            {
                hash ^= this.Name.GetHashCode();
            }

            if (this.Value != null)
            {
                hash ^= this.Value.GetHashCode();
            }

            return hash;
        }
    }

    [Serializable]
    public class MrsCategoryReputation
    {
        private uint m_categoryId;
        private byte m_confidence;
        private List<MrsMetadata> m_categoryInfo;

        public MrsCategoryReputation()
        {
        }

        public MrsCategoryReputation(uint categoryId, byte confidence, IList<MrsMetadata> categoryInfo)
        {
            this.m_categoryId = categoryId;
            this.m_confidence = confidence;

            if (categoryInfo != null)
            {
                this.m_categoryInfo = new List<MrsMetadata>(categoryInfo);
            }
        }


        [ReadOnly(true)]
        public uint CategoryId
        {
            get
            {
                return this.m_categoryId;
            }

            set
            {
                this.m_categoryId = value;
            }
        }

        [ReadOnly(true)]
        public byte Confidence
        {
            get
            {
                return this.m_confidence;
            }

            set
            {
                this.m_confidence = value;
            }
        }

        [ReadOnly(true)]
        public IList<MrsMetadata> CategoryInfo
        {
            get
            {
                return this.m_categoryInfo;
            }

            set
            {
                if (value == null)
                {
                    this.m_categoryInfo = null;
                }
                else
                {
                    this.m_categoryInfo = new List<MrsMetadata>(value);
                }
            }
        }

        public override bool Equals(object obj)
        {
            MrsCategoryReputation convOther = obj as MrsCategoryReputation;
            return Equals(convOther);
        }

        public bool Equals(MrsCategoryReputation otherObj)
        {
            bool metadataAreEqual = false;
            if (this.CategoryInfo == null || otherObj.CategoryInfo == null)
            {
                if (this.CategoryInfo == null && otherObj.CategoryInfo == null)
                {
                    metadataAreEqual = true;
                }
                else
                {
                    metadataAreEqual = false;
                }
            }
            else
            {
                metadataAreEqual = this.CategoryInfo.Count == otherObj.CategoryInfo.Count;

                foreach (MrsMetadata m in this.CategoryInfo)
                {
                    if (!otherObj.CategoryInfo.Contains(m))
                    {
                        metadataAreEqual = false;
                        break;
                    }
                }

                foreach (MrsMetadata m in otherObj.CategoryInfo)
                {
                    if (!this.CategoryInfo.Contains(m))
                    {
                        metadataAreEqual = false;
                        break;
                    }
                }
            }

            return (metadataAreEqual &&
                    (this.CategoryId == otherObj.CategoryId) &&
                    (this.Confidence == otherObj.Confidence));
        }

        public override int GetHashCode()
        {
            int hash = 0;

            hash ^= this.CategoryId.GetHashCode();
            hash ^= this.Confidence.GetHashCode();

            if (this.CategoryInfo != null)
            {
                hash ^= this.CategoryInfo.Count.GetHashCode();
            }


            return hash;
        }
    }


    [Serializable]
    public class MrsReputationObject
    {
        private uint m_type;
        private Guid m_guid; //GUIDs are no more used but LookupResponses from Delivery Service still contain GUID whose value in always GUID.Empty
        private List<MrsMetadata> m_metadata;
        private List<MrsCategoryReputation> m_reputation;
        private long m_version;
        private uint m_expirationMins;

        private MrsReputationObject()
        {
            this.m_type = MrsObjectTypes.None;
            this.m_guid = Guid.Empty;
            this.m_metadata = null;
            this.m_reputation = null;
            this.m_version = new long();
        }


        public MrsReputationObject(uint objType)
            : this()
        {
            this.m_type = objType;
        }

        public uint Type
        {
            get
            {
                return this.m_type;
            }

            set
            {
                this.m_type = value;
            }
        }

        public Guid Guid
        {
            get
            {
                return this.m_guid;
            }

            set
            {
                this.m_guid = value;
            }
        }

        public IList<MrsMetadata> Metadata
        {
            get
            {
                return this.m_metadata;
            }
        }

        public IList<MrsCategoryReputation> Reputation
        {
            get
            {
                return this.m_reputation;
            }
        }

        public long Version
        {
            get
            {
                return this.m_version;
            }
        }

        public uint ExpirationMins
        {
            get
            {
                return this.m_expirationMins;
            }
        }

        /// <summary>
        /// Updates the MrsReputationObject object with the version, metadata, reputation and expiration mins specified
        /// </summary>
        /// <param name="version"></param>
        /// <param name="metadata"></param>
        /// <param name="reputation"></param>
        /// <param name="expMins"></param>
        public void Update(long version, IList<MrsMetadata> metadata, IList<MrsCategoryReputation> reputation, uint expMins)
        {
            this.m_version = version;

            if ((metadata != null) && (metadata.Count > 0))
            {
                if (this.m_metadata != metadata)
                {
                    this.m_metadata = new List<MrsMetadata>(metadata);
                }
            }
            else
            {
                this.m_metadata = null;
            }

            if ((reputation != null) && (reputation.Count > 0))
            {
                if (this.m_reputation != reputation)
                {
                    this.m_reputation = new List<MrsCategoryReputation>(reputation);
                }
            }
            else
            {
                this.m_reputation = null;
            }

            this.m_expirationMins = expMins;
        }

        public bool Compare(MrsReputationObject obj)
        {
            bool isEqual = true;

            if (obj == null)
            {
                isEqual = false;
                Console.WriteLine("1. Object doesn't match");
            }

            if ((this.Type != obj.Type) && (this.Guid != obj.Guid))
            {
                isEqual = false;
                Console.WriteLine("2. Expected Object Type {0}, Actual Object Type {1}, Expected Object Guid {2}, Actual Object Guid {3}", this.Type, obj.Type, this.Guid, obj.Guid);
            }

            if (((this.Metadata == null) || (obj.Metadata == null)) &&
                (this.Metadata != obj.Metadata))
            {
                isEqual = false;
                Console.WriteLine("3. Metadata doesn't match");
            }

            if (this.Metadata != null)
            {
                if (this.Metadata.Count != obj.Metadata.Count)
                {
                    isEqual = false;
                    Console.WriteLine("4. Expected Metadata Count {0}, Actual Metadata Count {1}", this.Metadata.Count, obj.Metadata.Count);
                }

                foreach (MrsMetadata curMd in this.Metadata)
                {
                    if (!obj.Metadata.Contains(curMd))
                    {
                        isEqual = false;
                        Console.WriteLine("5. Metadata doesn't match");
                    }
                }
            }

            if (((this.Reputation == null) || (obj.Reputation == null)) &&
                (this.Reputation != obj.Reputation))
            {
                isEqual = false;
                Console.WriteLine("6. Reputation Data doesn't match");
            }

            if (this.Reputation != null)
            {
                if (this.Reputation.Count != obj.Reputation.Count)
                {
                    isEqual = false;
                    Console.WriteLine("7. Expected Reputation Count {0}, Actual Reputation Count {1}", this.Reputation.Count, obj.Reputation.Count);
                }

                foreach (MrsCategoryReputation curRep in this.Reputation)
                {
                    if (!obj.Reputation.Contains(curRep))
                    {
                        isEqual = false;
                        Console.WriteLine("8. Category Data doesn't match");
                    }
                }
            }

            if ((this.Version != obj.Version))
            {
                isEqual = false;
                Console.WriteLine("9. Expected Version {0}, Actual Version {1}", this.Version, obj.Version);
            }

            if((this.ExpirationMins != obj.ExpirationMins))
            {
                isEqual = false;
                Console.WriteLine("10. Expected Expiration Mins {0}, Actual Expiration Mins {1}", this.ExpirationMins, obj.ExpirationMins);
            }

            return isEqual;
        }

        /// <summary>
        /// Applied filter on the MrsReputation object returned from web service cache or DB
        /// </summary>
        /// <param name="filter">Filter with the requested type, metadata and categories</param>
        public void FilterOut(MrsFilter filter)
        {
            FilterByDataType(filter.Types);
            FilterByMetadata(filter.Metadata);
            FilterByCategory(filter.Categories);
        }

        /// <summary>
        /// Applies filter on test objects based on the types requested 
        /// </summary>
        /// <param name="dataTypes">List of data types - 0 for Reputation and 1 for Metadata</param>
        private void FilterByDataType(IList<int> dataTypes)
        {
            if (dataTypes != null)
            {
                if (false == dataTypes.Contains(MrsRequestDataTypes.Metadata))
                {
                    this.Update(this.Version, null, this.Reputation, this.ExpirationMins);
                }

                if (false == dataTypes.Contains(MrsRequestDataTypes.Reputation))
                {
                    this.Update(this.Version, this.Metadata, null, this.ExpirationMins);
                }
            }
        }

        /// <summary>
        /// Applies filter on test objects based on the requested categories
        /// </summary>
        /// <param name="categories">List of category Ids</param>
        private void FilterByCategory(IList<int> categories)
        {
            if (this.Reputation != null && categories != null)
            {
                //Look at each category of the Reputation list and if the category id matches the filter category request
                //then keep that reputation object, else remove it from the List
                for (int idx = this.Reputation.Count - 1; idx >= 0; idx--)
                {
                    if (false == categories.Contains((int)this.Reputation[idx].CategoryId))
                    {
                        this.Reputation.RemoveAt(idx);
                    }
                }

                if (this.Reputation.Count == 0)
                {
                    this.Update(this.Version, this.Metadata, null, this.ExpirationMins);
                }
            }
        }

        /// <summary>
        /// Applies filter on test objects based on requested metadata
        /// </summary>
        /// <param name="metadata">List of metadata strings</param>
        private void FilterByMetadata(IList<string> metadata)
        {
            if (this.Metadata != null && metadata != null)
            {
                for (int idx = this.Metadata.Count - 1; idx >= 0; idx--)
                {
                    bool isMetadataNeeded = false;
                    foreach (string data in metadata)
                    {
                        if (data.Equals(this.Metadata[idx].Name, StringComparison.OrdinalIgnoreCase))
                        {
                            isMetadataNeeded = true;
                        }
                    }

                    if (false == isMetadataNeeded)
                    {
                        this.Metadata.RemoveAt(idx);
                    }
                }

                if (this.Metadata.Count == 0)
                {
                    this.Update(this.Version, null, this.Reputation, this.ExpirationMins);
                }
            }
        }

        /// <summary>
        /// Finds metadata for a given searchable field. For search fields only one metadata should exist
        /// </summary>
        /// <param name="fieldName"></param>
        /// <returns></returns>
        public MrsMetadata FindMetadataForSearchField(string fieldName)
        {
            MrsMetadata result = null;
            if (String.IsNullOrEmpty(fieldName))
            {
                result = null;
            }

            if (this.Metadata == null)
            {
                result = null;
            }
            
            foreach (MrsMetadata curMd in this.Metadata)
            {
                if (curMd.Name.Equals(fieldName, StringComparison.OrdinalIgnoreCase))
                {
                    result = curMd;
                }
            }
            return result;
        }

        /// <summary>
        /// Finds the specified metadata in the Metadata list of the object
        /// </summary>
        /// <param name="mdName">metadata name to search for</param>
        /// <returns>List of MrsMetadata objects which match the search string</returns>
        public IList<MrsMetadata> FindMetadata(string mdName)
        {
            if (String.IsNullOrEmpty(mdName))
            {
                return null;
            }

            if (this.Metadata == null)
            {
                return null;
            }

            List<MrsMetadata> res = new List<MrsMetadata>();

            foreach (MrsMetadata curMd in this.Metadata)
            {
                if (curMd.Name.Equals(mdName, StringComparison.OrdinalIgnoreCase))
                {
                    res.Add(curMd);
                }
            }

            return res;
        }

        /// <summary>
        /// Finds the specified category in the Category list of the object
        /// </summary>
        /// <param name="category">category id of the category to search for</param>
        /// <returns>MrsCategoryReputation object which matches the search category</returns>
        public MrsCategoryReputation FindReputation(uint category)
        {
            if (this.Reputation == null)
            {
                return null;
            }

            foreach (MrsCategoryReputation curRep in this.Reputation)
            {
                if (curRep.CategoryId == category)
                {
                    return curRep;
                }
            }

            return null;
        }

        public void Print()
        {
            Console.WriteLine("\nReputation Object");
            Console.WriteLine("===================");
            Console.WriteLine("Type = {0}", this.Type);
            Console.WriteLine("Guid = {0}", this.Guid);
            if (this.Metadata != null)
            {
                Console.WriteLine("Metadata");
                foreach (MrsMetadata curMd in this.Metadata)
                {
                    Console.WriteLine("\tName = {0}", curMd.Name);
                    Console.WriteLine("\tValue = {0}", Convertor.ConvertByteArrayToString((byte[])curMd.Value));
                }
            }

            if (this.Reputation != null)
            {
                Console.WriteLine("Reputation");
                foreach (MrsCategoryReputation curRep in this.Reputation)
                {
                    Console.WriteLine("\tCategoryId = {0}", curRep.CategoryId);
                    Console.WriteLine("\tConfidence = {0}", curRep.Confidence);
                    Console.WriteLine("\tCategoryInfo");
                    foreach (MrsMetadata repMeta in curRep.CategoryInfo)
                    {
                        Console.WriteLine("\t\tName = {0}", repMeta.Name);
                        Console.WriteLine("\t\tValue = {0}", repMeta.Value.ToString());
                    }
                }
            }

            Console.WriteLine("Version = {0}", this.Version);
            Console.WriteLine("Expiration Mins = {0}\n", this.ExpirationMins);            
        }

        #region Overridden methods
        public override bool Equals(object obj)
        {
            if ((obj == null) || (!obj.GetType().Equals(this.GetType())))
            {
                return false;
            }

            MrsReputationObject otherObj = (MrsReputationObject)(obj);

            return this.Compare(otherObj);
        }

        public override int GetHashCode()
        {
            return this.m_type.GetHashCode() ^ this.m_guid.GetHashCode() ^ this.m_expirationMins.GetHashCode() ^ this.m_version.GetHashCode();
        }
        #endregion

        public static MrsReputationObject TranslateFromUrfReputationObject(UrfReputationObject obj)
        {
            MrsReputationObject resObj = new MrsReputationObject(obj.Type);
            List<MrsMetadata> mdList = null;
            List<MrsCategoryReputation> crList = null;

            if ((obj.Hash != null) || (obj.Bloom != null))
            {
                mdList = new List<MrsMetadata>();
            }

            if (obj.Hash != null)
            {
                mdList.Add(new MrsMetadata("SHA256", obj.Hash));
            }
            
            if (obj.Bloom != null)
            {
                mdList.Add(new MrsMetadata("Bloom", obj.Bloom));
            }

            if ((obj.Reputation != null) && (obj.Reputation.Count() != 0))
            {
                crList = new List<MrsCategoryReputation>();

                foreach (ReputationCategory curCr in obj.Reputation)
                {
                    crList.Add(TranslateFromReputationCategory(curCr));
                }
            }
            resObj.Update(obj.Version, mdList, crList, obj.ClientExpirationMinutes);
            return resObj;
        }

        private static MrsCategoryReputation TranslateFromReputationCategory(ReputationCategory curCr)
        {
            MrsCategoryReputation cat = new MrsCategoryReputation();
            cat.CategoryId = (uint)curCr.CategoryId;
            cat.Confidence = (byte)curCr.Confidence;
            List<MrsMetadata> mdList = new List<MrsMetadata>();
            mdList.Add(new MrsMetadata("HostInheritance", curCr.HostInheritance == false ? "0" : "1"));
            mdList.Add(new MrsMetadata("PathInheritance", curCr.PathInheritance == false ? "0" : "1"));
            cat.CategoryInfo = mdList;
            return cat;
        }
    }


    /// <summary>
    /// Holds the Test MrsLookupRequest Object that maps to one LookupRequest call
    /// </summary>
    [Serializable]
    public class MrsLookupRequest
    {
        private uint m_type;
        private List<MrsMetadata> m_searchFields;

        /// <summary>
        /// Constructs MrsLookupRequest object with the given Object Id and searchable fields
        /// </summary>
        /// <param name="requestObj">Holds the Guid and Type fields for a LookupRequest call, looks for object of this type</param>
        /// <param name="searchFields">Used as SearchableFields for Lookup by metadata tests</param>
        public MrsLookupRequest(uint type, IList<MrsMetadata> searchFields)
        {
            m_type = type;
            m_searchFields = (searchFields == null) ? null : new List<MrsMetadata>(searchFields);
        }

        /// <summary>
        /// Constructs MrsLookupRequest with given Object Id and null searchable fields
        /// </summary>
        /// <param name="requestObj">Holds the Guid and Type fields for a LookupRequest call, looks for object of this type</param>
        public MrsLookupRequest(uint type): this(type, null) {}

        public uint Type
        {
            get
            {
                return this.m_type;
            }
        
            set
            {
                this.m_type = value;
            }
        }

        public IList<MrsMetadata> SearchFields
        {
            get
            {
                return this.m_searchFields;
            }

            set
            {
                if (value != null)
                {
                    this.m_searchFields = new List<MrsMetadata>(value);
                }
                else
                {
                    this.m_searchFields = null;
                }
            }
        }
    }

    [Serializable]
    public class MrsLookupResponse
    {
        private uint m_seqNum;
        private uint m_status;
        private List<MrsReputationObject> m_matches;
        private long? m_expirationMins;

        private const long expMinsNoMatch = 10800;
        private const uint statusNoMatch = 4098;

        /// <summary>
        /// Constructs a MrsLookupResult object containing the sequence number, status of the request, telemetry data if any,
        /// Matches if any and expiration mins for the match
        /// </summary>
        /// <param name="seqNum">Sequence number corresponding to the request, each lookup request has a corresponding result</param>
        /// <param name="status">Status of the lookup request, 0 means success, else an error. For error codes refer TestStatusCodes.cs</param>
        /// <param name="matches">Holds the matches found as returned by the web service for the lookup request made</param>
        /// <param name="expMins">Expiration Mins on the match found - used for client side caching</param>
        public MrsLookupResponse(uint seqNum, uint status, List<MrsReputationObject> matches, long? expMins)
        {
            this.m_seqNum = seqNum;
            this.m_status = status;
            this.m_matches = matches;
            this.m_expirationMins = expMins;
        }

        public uint SeqNum
        {
            get
            {
                return this.m_seqNum;
            }
        }

        public uint Status
        {
            get
            {
                return this.m_status;
            }
        }

        public List<MrsReputationObject> Matches
        {
            get
            {
                return this.m_matches;
            }
        }

        public long? ExpirationMins
        {
            get
            {
                return this.m_expirationMins;
            }
        }

        public MrsReputationObject FindMrsReputationObjectWithHash(byte[] sha256)
        {
            string sha256Base64 = Convert.ToBase64String(sha256);

            if (Matches == null)
            {
                return null;
            }

            return Matches.SingleOrDefault(
                ro => Convert.ToBase64String((byte[])ro.Metadata.Single(
                    meta => meta.Name == "SHA256").Value) == sha256Base64);
        }

        /// <summary>
        /// Verify is the Lookup response is same as expected null match
        /// </summary>
        /// <param name="result">Result for a variant as returned by the web service</param>
        /// <returns>True if the variant result is a null match, else False</returns>
        public bool IsNullMatch()
        {
            bool isMatch = true;

            if (this.ExpirationMins != expMinsNoMatch)
            {
                isMatch = false;
                Log.Comment("FAIL: Expiration Mins do not match");
            }
            if (this.Matches != null)
            {
                isMatch = false;
                Log.Comment("FAIL: Response Matches should be null");
            }
            if (this.Status != statusNoMatch)
            {
                isMatch = false;
                Log.Comment("FAIL: Status does not match");
            }
            return isMatch;
        }
    }
}
